﻿using Furion;
using Furion.DataEncryption;
using Furion.DependencyInjection;
using Furion.DynamicApiController;
using Furion.FriendlyException;
using HiSportApplet.Application.Common.SqlSugar;
using HiSportApplet.Application.Common.WeChatOpen;
using HiSportApplet.Application.Common.WeChatOpen.Dtos;
using HiSportApplet.Application.Service.Auth.Dtos;
using HiSportApplet.Application.Service.Auth.Dtos.EncryptKeyDtos;
using HiSportApplet.Application.Service.Auth.Dtos.GetPhoneDtos;
using HiSportApplet.Core.Const;
using HiSportApplet.Core.Entity.Member;
using HiSportApplet.Core.Enum;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System.ComponentModel;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;

namespace HiSportApplet.Application.Service.Auth
{
    /// <summary>
    /// 系统登录授权服务
    /// </summary>
    [ApiDescriptionSettings("授权", Tag = "授权", SplitCamelCase = false)]
    public class SysAuthService : IDynamicApiController, ITransient
    {
        private readonly SqlSugarRepository<SysMember> _sysMember;
        private readonly WeChatOpenSettings _weixinOpenSettings;
        private readonly IHttpContextAccessor _httpContextAccessor;

        public SysAuthService(
            SqlSugarRepository<SysMember> sysMember,
            IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
            _weixinOpenSettings = new WeChatOpenSettings();
            _sysMember = sysMember;
        }
        static Dictionary<string, LoginOutput> collection = new Dictionary<string, LoginOutput>();
        private readonly object @lock = new object();


        /// <summary>
        /// 获取微信接口调用凭据-TOKEN
        /// </summary>
        /// <returns></returns>
        [DisplayName("获取微信接口调用凭据(TOKEN)")]
        [HttpGet("GetToken")]
        public async Task<string> GetToken()
        {

            var appId = _weixinOpenSettings.AppId;
            var secret = _weixinOpenSettings.AppSecret;

            //TODO:token一般2小时过期一次,需要采用缓存的模式，不要每次都进行获取，token有次数限制
            var data = await WeChatOpenService.GetToken(appId, secret);
            return data;
        }

        /// <summary>
        /// 根据Code
        /// 查询小程序的OpenId和SessionKey
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [DisplayName("查询小程序的OpenId和SessionKey")]
        [HttpPost("SearchOpenIdAndSessionKey")]
        public OpenIdAndSessionKeyOutPutDto SearchOpenIdAndSessionKey(OpenIdAndSessionKeyInputDto input)
        {
            #region 参数验证
            if (input == null) throw Oops.Oh("参数有误");
            var code = input.Code;
            if (string.IsNullOrWhiteSpace(code))
            {
                throw Oops.Oh("参数有误：code必填");
            }

            #endregion

            var oiask = new WeChatOpenService().GetOpenIdAndSessionKey(code, _weixinOpenSettings.AppId, _weixinOpenSettings.AppSecret);
            if (oiask == null) throw Oops.Oh("参数有误：未获取到微信相关信息");

            if (!string.IsNullOrWhiteSpace(oiask.errcode)) throw Oops.Oh($"errcode:{oiask.errcode},errmsg:{oiask.errmsg}");

            return new OpenIdAndSessionKeyOutPutDto { Openid = oiask.openid, UnionId = oiask.unionid, SessionKey = oiask.session_key };


        }


        /// <summary>
        /// 获取用户EncryptKey和加密IV
        /// </summary>
        /// <returns></returns>
        [DisplayName("获取用户EncryptKey")]
        [HttpPost("GetEncryptKey")]

        public async Task<KeyInforList> GetEncryptKey(GetEncryptKeyInputDto input)
        {

            //获取token
            var token = await GetToken();

            var openid = input.OpenId;
            var session_key = input.SessionKey;

            var signature = HmacSha256(session_key, "");
            var param = new EncryptKeyRequest
            {
                Token = token,
                OpenId = openid,
                sig_method = "hmac_sha256",
                signature = signature
            };
            var result = await WeChatOpenService.EncryptKey(param);
            if (result == null || !result.Any()) { return new KeyInforList(); }
            else
            {
                return result.FirstOrDefault(a => a.expire_in > 0);
            }
        }


        /// <summary>
        /// 根据openid查询是否有系统会员数据，
        /// 如果返回的是NULL，或者返回的没有ID数据则代表没有对应的系统会员
        /// </summary>
        /// <returns></returns>
        [DisplayName("查询系统会员数据")]
        [HttpPost("SearchSysMember")]
        public SysMemberBasicDto SearchSysMember(SearchSysMember input)
        {
            #region 参数验证

            if (input == null) throw Oops.Oh("参数有误");
            var openid = input.OpenId;
            if (string.IsNullOrWhiteSpace(openid))
            {
                throw Oops.Oh("参数有误：openid必填");
            }

            #endregion

            #region 查询是否有系统会员

            var sysMember = _sysMember.AsQueryable().Where(a => a.OpenId == openid && a.IsDelete == false).ToList();
            if (sysMember == null || !sysMember.Any()) return null;

            if (sysMember.Count() > 1) throw Oops.Oh("数据有误:此openid对应的会员存在多个");

            return sysMember[0].Adapt<SysMemberBasicDto>();

            #endregion
        }

        /// <summary>
        /// 注册系统会员或者修改会员信息
        /// </summary>
        /// <returns></returns>
        [DisplayName("注册系统会员或者修改会员信息")]
        [HttpPost("CreateSysMember")]
        public async Task<SysMemberBasicDto> CreateSysMember(CreateSysMemberInputDto input)
        {
            //TODO:要使用锁 防止重复增加

            #region 参数验证

            if (input == null) throw Oops.Oh("参数有误");
            var openid = input.OpenId;
            if (string.IsNullOrWhiteSpace(openid))
            {
                throw Oops.Oh("参数有误：openid必填");
            }
            var phone = input.Phone;
            if (string.IsNullOrWhiteSpace(phone))
            {
                throw Oops.Oh("参数有误：手机号必填");
            }
            #endregion


            #region 查询是否有系统会员

            var sysMember = _sysMember.AsQueryable().Where(a => a.OpenId == openid && a.IsDelete == false).ToList();
            if (sysMember != null && sysMember.Any())
            {
                if (sysMember.Count() > 1) throw Oops.Oh("数据有误:此openid对应的会员存在多个");
                var curMember = sysMember[0];
                curMember.Phone = phone;
                curMember.OpenId = input.OpenId;
                curMember.Name = input.NickName;
                curMember.HeadUrl = input.HeadUrl;
                curMember.Sex = input.Sex;
                await _sysMember.AsUpdateable().ExecuteCommandAsync();
                return curMember.Adapt<SysMemberBasicDto>();
            }
            else
            {
                //保存会员信息            
                SysMember memberinfo = new SysMember
                {
                    Phone = phone,
                    OpenId = input.OpenId,
                    Name = input.NickName,
                    HeadUrl = input.HeadUrl,
                    Sex = input.Sex
                };

                SetDateTimeAndStringDefaultValue(memberinfo);
                var curMember = await _sysMember.AsInsertable(memberinfo).ExecuteReturnEntityAsync();
                if (curMember == null) throw Oops.Oh("注册失败");
                var result = curMember.Adapt<SysMemberBasicDto>();
                return result;
            }
            #endregion


        }


        ///// <summary>
        ///// 获取用户EncryptKey和加密IV
        ///// </summary>
        ///// <returns></returns>
        //[DisplayName("获取用户EncryptKey")]
        //[HttpPost("GetEncryptKey")]
        //public async Task<List<KeyInforList>> GetEncryptKey(string code)
        //{
        //    var appId = _weixinOpenSettings.AppId;
        //    var secret = _weixinOpenSettings.AppSecret;

        //    //获取token
        //    var token = await GetToken();

        //    //获取用户的sig_method
        //    var data = SearchOpenIdAndSessionKey(new OpenIdAndSessionKeyInputDto {Code=code });


        //    var openid = data.Openid;
        //    var session_key = data.SessionKey;

        //    var signature = HmacSha256(session_key, "");
        //    var param = new EncryptKeyRequest
        //    {
        //        Token = token,
        //        OpenId = openid,
        //        sig_method = "hmac_sha256",
        //        signature = signature
        //    };
        //    var result = await WeChatOpenService.EncryptKey(param);
        //    if(result==null || !result.Any())
        //    return result;
        //}





        /// <summary>
        /// 微信小程序授权登录
        /// </summary>
        /// <param name="code"></param>
        /// <param name="encryptedData">加密的数据 用户的基本信息</param>
        /// <param name="iv">加密iv</param>
        /// <returns></returns>
        [AllowAnonymous]
        [DisplayName("微信小程序授权登录")]
        [HttpGet("login")]

        public async Task<LoginOutput> LoginApi(string code, string encryptedData = "", string iv = "")
        {
            LoginOutput result = new LoginOutput();
            var secondRequest = false;
            lock (@lock)
            {
                secondRequest = collection.ContainsKey(code);
            }

            if (!secondRequest)
            {
                //第一次请求
                lock (@lock)
                {
                    collection[code] = null;
                }
            }
            else
            {
                //第二次请求
                lock (@lock)
                {
                    result = collection[code];
                }
            }
            try
            {
                var oiask = new WeChatOpenService().GetOpenIdAndSessionKey(code, _weixinOpenSettings.AppId, _weixinOpenSettings.AppSecret);

                if (oiask == null)
                    throw Oops.Oh(ErrorCodeEnum.D1025);


                if (!string.IsNullOrEmpty(oiask.errmsg))
                    throw Oops.Oh(string.Format("回调异常,errcode:{0} errmsg:{1}", oiask.errcode, oiask.errmsg));

                var memberFound = await _sysMember.AsQueryable().Where(a => a.OpenId == oiask.openid).FirstAsync();
                //会员不存在新创建
                if (memberFound == null)
                {
                    var userInfo = new WeChatOpenService().GetUser(encryptedData, iv, oiask.session_key) ?? new WeixinAppUserInfo();
                    if (userInfo == null) throw Oops.Oh(string.Format("未获取到用户信息"));


                    SysMember memberinfo = new SysMember();
                    memberinfo.HeadUrl = userInfo.avatarUrl;
                    memberinfo.Sex = userInfo.gender == null || !userInfo.gender.HasValue ? 0 : (GenderEnum)userInfo.gender;
                    memberinfo.OpenId = userInfo.openId;
                    SetDateTimeAndStringDefaultValue(memberinfo);
                    //注册会员
                    var isAaddMember = _sysMember.InsertOrUpdate(memberinfo);
                    if (isAaddMember)
                    {
                        memberFound = memberinfo;
                    }
                    else
                    {
                        throw Oops.Oh(string.Format("注册失败"));
                    }

                }

                var token = await CreateToken(memberFound);

                result.openId = memberFound.OpenId;
                result.AccessToken = token.AccessToken;
                result.RefreshToken = token.RefreshToken;
            }
            catch (Exception ex)
            {
                throw Oops.Oh(string.Format("错误了"));
            }
            if (result != null)
            {
                lock (@lock)
                {
                    collection[code] = result;
                }
            }

            return result;
        }


        /// <summary>
        /// 获取刷新Token
        /// </summary>
        /// <param name="accessToken"></param>
        /// <returns></returns>
        [DisplayName("获取刷新Token")]
        public string GetRefreshToken([FromQuery] string accessToken)
        {
            return JWTEncryption.GenerateRefreshToken(accessToken);
        }

        /// <summary>
        /// 生成Token令牌
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        [NonAction]
        public async Task<LoginOutput> CreateToken(SysMember member)
        {
            //登陆 保存获取token
            var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
                {
                    { ClaimConst.OpenId, member.OpenId },
                    { ClaimConst.Phone, member.Phone },
                     { ClaimConst.UserId, member.Id },
                });

            // 生成刷新Token令牌
            var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken);

            // 设置响应报文头
            _httpContextAccessor.HttpContext.SetTokensOfResponseHeaders(accessToken, refreshToken);

            // Swagger Knife4UI-AfterScript登录脚本
            // ke.global.setAllHeader('Authorization', 'Bearer ' + ke.response.headers['access-token']);

            return new LoginOutput
            {
                AccessToken = accessToken,
                RefreshToken = refreshToken
            };
        }


        /// <summary>
        /// 获取微信小程序的手机号
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [DisplayName("获取微信小程序的手机号")]
        [HttpPost("GetPhone")]
        public async Task<PhoneInfo> GetPhone(GetPhoneRequest input)
        {
            var token = await GetToken();
            var param = JsonConvert.SerializeObject(input);
            var result = await new WeChatOpenService().GetPhoneNumber2(token, param);

            var data = JsonConvert.DeserializeObject<GetPhoneResponse>(result);
            if (data == null) throw Oops.Oh("获取手机号失败:未得到任何数据");
            if (data.errcode != 0) throw Oops.Oh($"获取手机号失败:errcode:{data.errcode},errmsg:{data.errmsg}");
            return data.phone_info;

        }


        /// <summary>
        /// 获取微信小程序的手机号-
        /// </summary>
        /// <param name="encryptedData">加密数据</param>
        /// <param name="iv"></param>
        /// <param name="session_ke"></param>
        /// <returns></returns>
        [DisplayName("获取微信小程序的手机号2")]
        [HttpGet("GetPhone2")]
        public WeixinAppPhoneNumber GetPhone2([FromQuery] string encryptedData, [FromQuery] string iv, [FromQuery] string session_ke)
        {

            return new WeChatOpenService().GetPhoneNumber(encryptedData, iv, session_ke);

        }


        #region 私有方法

        /// <summary>
        /// 用HmacSHA256方法，以session_key为key,对字符串进行签名
        /// 以Hex字符串格式输出
        /// </summary>
        /// <param name="secretKey"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        private string HmacSha256(string secretKey, string message)
        {
            byte[] keyByte = Encoding.UTF8.GetBytes(secretKey);
            byte[] messageBytes = Encoding.UTF8.GetBytes(message);

            using (HMACSHA256 hmacsha256 = new HMACSHA256(keyByte))
            {
                byte[] hashValue = hmacsha256.ComputeHash(messageBytes);
                string hexString = BitConverter.ToString(hashValue).Replace("-", "").ToLowerInvariant();
                return hexString;
            }
        }

        /// <summary>
        /// 将所有属性类型为DateTime，可空DateTime的默认值重写改为3000-12-31，string类型的默认值重写为string.Empty
        /// </summary>
        private void SetDateTimeAndStringDefaultValue(object? entity)
        {
            //var entity = entry.Entity;
            foreach (var property in entity.GetType().GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                // 设置DateTime和可空DateTime，默认值
                if ((property.PropertyType == typeof(DateTime) || property.PropertyType == typeof(DateTime?))
                    && property.CanWrite
                    && property.CanRead
                    && (Equals(property.GetValue(entity), DateTime.MinValue) || Equals(property.GetValue(entity), null)))
                {
                    property.SetValue(entity, Convert.ToDateTime("3000-12-31 00:00:00"));
                }
                // 设置字符串，默认值
                if ((property.PropertyType == typeof(string))
                    && property.CanWrite
                    && property.CanRead
                    && (property.GetValue(entity) == null))
                {
                    property.SetValue(entity, string.Empty);
                }
            }
        }
        #endregion

    }
}
